package x.ovo.wechat.bot.http;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.system.SystemUtil;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import x.ovo.wechat.bot.common.Constant;
import x.ovo.wechat.bot.common.Context;
import x.ovo.wechat.bot.common.entity.*;
import x.ovo.wechat.bot.common.entity.event.LoginSystemEvent;
import x.ovo.wechat.bot.common.entity.event.SystemEvent;
import x.ovo.wechat.bot.common.entity.message.SendMessage;
import x.ovo.wechat.bot.common.enums.LoginMode;
import x.ovo.wechat.bot.common.enums.MessageType;
import x.ovo.wechat.bot.common.exception.ApiException;
import x.ovo.wechat.bot.common.http.HttpEngine;
import x.ovo.wechat.bot.common.http.WechatApi;
import x.ovo.wechat.bot.common.http.request.BaseRequest;
import x.ovo.wechat.bot.common.http.session.LoginSession;
import x.ovo.wechat.bot.core.Config;
import x.ovo.wechat.bot.http.request.contact.*;
import x.ovo.wechat.bot.http.request.core.*;
import x.ovo.wechat.bot.http.request.group.CreateGroupRequest;
import x.ovo.wechat.bot.http.request.group.InviteMemberRequest;
import x.ovo.wechat.bot.http.request.group.ModifyGroupNameRequest;
import x.ovo.wechat.bot.http.request.group.RemoveMemberRequest;
import x.ovo.wechat.bot.http.request.message.*;
import x.ovo.wechat.bot.http.response.InitResponse;
import x.ovo.wechat.bot.http.response.QRCodeResponse;
import x.ovo.wechat.bot.http.response.StatusNotifyResponse;
import x.ovo.wechat.bot.login.HotReload;

import java.awt.*;
import java.io.File;
import java.net.HttpCookie;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 微信api的默认实现
 *
 * @author ovo, created by 2024/05/03
 */
@Slf4j
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DefaultWeChatApi implements WechatApi {

    private Context context;
    private BaseRequest baseRequest;
    private HttpEngine<?> engine;
    @NonNull private Config config;
    @Getter @NonNull private LoginSession loginSession;

    public static DefaultWeChatApi create() {
        // 创建api和loginSession
        DefaultWeChatApi api = new DefaultWeChatApi(Config.get(), new LoginSession());
        api.engine = DefaultHttpEngine.create();
        return api;
    }

    @Override
    public void setContext(Context context) {
        this.context = context;
        context.setHttpEngine(this.engine);
    }

    @Override
    public void init() {
        // 初始化微信，只有登录成功后且非自动登录才初始化，自动登录后再初始化会获取扫码登录后的syncKey，造成消息重复
        if (this.context.isLogin() && !this.context.isAutoLogedin()) this.initWeChat();
        // 开启状态通知
        this.statusNotify(null);
        // 将loginSession放入context
        this.context.setLoginSession(this.loginSession);
    }

    @Override
    public void login(boolean autoLogin) {
        if (this.context.isLogin()) {
            log.warn("已经登录，无需重复登录");
            return;
        }
        if (autoLogin) {
            this.autoLogin();
        } else {
            while (!this.context.isLogin()) {
                // 获取二维码UUID
                AtomicInteger tip = new AtomicInteger(1);
                String uuid = Optional.ofNullable(this.pushLogin()).orElseGet(() -> {
                    tip.set(0);
                    String s = this.getUUID();
                    this.printQRCode(s);
                    return s;
                });
                // 等待登录成功
                this.waitLogin(uuid, tip.get());
            }
        }
    }

    @Override
    @SneakyThrows
    public String pushLogin() {
        if (Objects.isNull(this.loginSession.getWxUin())) return null;
        return new PushLoginRequest(this.loginSession).execute(this.engine);
    }

    /**
     * 自动登录
     */
    private void autoLogin() {
        try {
            // 如果session中uin为空，则读取login.json获取HotReload对象，设置session、cookie
            if (StrUtil.isBlank(this.loginSession.getWxUin())) {
                HotReload hotReload = new HotReload();
                hotReload.reLogin();
                this.loginSession = Optional.ofNullable(hotReload.getSession()).orElse(this.loginSession);
                this.context.setLoginSession(this.loginSession);
                OkCookieJar.reverse(hotReload.getCookieStore());
            }
            // 通过获取自己的用户信息检测是否登录成功
            if (Objects.nonNull(this.getContactById(this.loginSession.getUserName()))) {
                this.context.setLogin(true);
                this.context.setAutoLogedin(true);
                this.context.getEventManager().callEvent(new LoginSystemEvent(this.loginSession.getWxUin() + " 自动登录成功"));
                log.info("自动登录成功, Uin: {}, Name: {}", this.loginSession.getWxUin(), this.loginSession.getNickName());
            }
//            // 通过存储的登录信息未能登录，则调用pushLogin()方法登录
//            this.pushLogin();
//            // 使用reLogin后，调用statusNotify()方法检测是否登录成功，如果失败则调用正常的扫码登录
//            if (!this.statusNotify(null)) {
//                log.error("自动登录失败");
//                this.login(false);
//                return;
//            }
        } catch (Exception e) {
            log.error("自动登录失败: {}", e.getMessage());
            this.login(false);
        }
    }


    /**
     * 获取 UUID
     *
     * @return {@link String}
     */
    @SneakyThrows
    private String getUUID() {
        return new UuidRequest(this.loginSession).execute(this.engine);
    }

    /**
     * 打印二维码
     *
     * @param uuid UUID
     */
    protected void printQRCode(String uuid) {
//        System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");
        String url = "https://login.weixin.qq.com/qrcode/" + uuid;
        try {
            String osName = SystemUtil.get("os.name");
            if (osName.toLowerCase().contains("win") || osName.toLowerCase().contains("mac")) {
                File file = HttpUtil.downloadFileFromUrl(url, new File(Constant.CONFIG_DIR, "qrcode.png"));
                Desktop.getDesktop().open(file);
                return;
            }
        } catch (Exception e) {
            log.error("打开二维码失败，请点击下方链接或扫描终端二维码登录", e);
        }
        log.info("请访问 {} 扫码进行登录", url);
        if (this.config.isPrintTerminal()) {
            System.out.println(QrCodeUtil.generateAsAsciiArt(url, QrConfig.create()));
        }
    }

    /**
     * 等待登录
     *
     * @param uuid UUID
     */
    private void waitLogin(String uuid, int tip) {
        QRCodeResponse response = null;
        // 轮询检查登录状态
        while (!this.context.isLogin()) {
            ThreadUtil.sleep(2000);
            response = this.checkLogin(uuid, tip);
            log.info("{}", response.getLoginMode().getMessage());
            this.context.setLogin(response.getLoginMode() == LoginMode.SUCCESS);
        }
        // 登录成功后，处理session、baseRequest信息
        this.baseRequest = this.getLoginInfo(response);
        this.context.getEventManager().callEvent(new LoginSystemEvent(this.loginSession.getWxUin()));
        log.info("登录成功");
        log.debug("登录信息: {}", JSONUtil.toJsonStr(this.baseRequest));
    }

    /**
     * 检查登录
     *
     * @param uuid UUID
     * @param tip  登录方式，0表示等待用户扫码确认；1表示等待用户直接确认，用于push登陆时；
     * @return {@link QRCodeResponse}
     */
    @SneakyThrows
    private QRCodeResponse checkLogin(String uuid, int tip) {
        return new CheckLoginRequest(this.loginSession, tip, uuid).execute(this.engine);
    }

    /**
     * 获取登录信息
     *
     * @param qrcodeResponse QRCODE 响应
     * @return {@link BaseRequest}
     */
    private BaseRequest getLoginInfo(QRCodeResponse qrcodeResponse) {
        return Optional.ofNullable(this.baseRequest).orElseGet(() -> {

            BaseRequest request = new LoginInfoRequest(this.loginSession, qrcodeResponse.getUrl()).execute(this.engine);

            List<HttpCookie> cookies = this.engine.getCookies();
            // 初始化登录会话
            this.loginSession.init(request, cookies);
            this.loginSession.setPassTicket(request.getPassTicket());
            // 遍历cookie获取其他信息
            cookies.forEach(cookie -> {
                if (cookie.getName().startsWith("webwx_auth_ticket")) this.loginSession.setAuthTicket(cookie.getValue());
                if (cookie.getName().startsWith("webwx_data_ticket")) this.loginSession.setDataTicket(cookie.getValue());
            });
            return request;
        });
    }


    @Override
    @SneakyThrows
    public void initWeChat() {
        InitResponse response = new InitRequest(this.loginSession).execute(this.engine);
        // 如果初始化失败，则重新登录
        if (!response.success()){
            this.context.setLogin(false);
            this.login(false);
        }
        List<Contact> accountList = response.getContactList();
        this.context.getContactManager().syncRecentContact(accountList);
        Contact user = response.getUser();
        this.loginSession.setContact(user);
        this.loginSession.setUserName(user.getUserName());
        this.loginSession.setNickName(user.getNickName());
        this.loginSession.setSyncKey(response.getSyncKey());
        this.loginSession.setSyncCheckKey(response.getSyncKey());
        this.loginSession.setInviteStartCount(response.getInviteStartCount());
        if (this.config.isAutoLogin()) {
            HotReload reload = HotReload.build(this.loginSession, OkCookieJar.getCookieStore());
            reload.save();
        }

        this.context.getEventManager().callEvent(new SystemEvent("初始化完成"));
        log.info("初始化完成，欢迎：{}", user.getNickName());
        log.debug("用户信息: {}", user);
    }

    @Override
    public boolean statusNotify(String userName) {
        StatusNotifyResponse response = new StatusNotifyRequest(this.loginSession, userName).execute(this.engine);
        return null != response && response.getBaseResponse().getRet() == 0;
    }

    @Override
    public void logout() {
        if (this.context.isLogin()) {
            this.context.setLogin(false);
//            this.context.setRunning(false);
            new LogoutRequest(this.loginSession).execute(this.engine);
        } else {
            log.warn("未登录，无法退出");
        }
    }

    @Override
    public SyncCheckResult syncCheck() {
        return new SyncCheckRequest(this.loginSession).execute(this.engine);
    }

    @Override
    public WebSyncResult webSync() {
        return new WebSyncRequest(this.loginSession).execute(this.engine);
    }

    @Override
    public boolean sendText(String toUserName, String content) {
        String msgId = System.currentTimeMillis() / 1000 + RandomUtil.randomString(6);
        SendMessage message = SendMessage.builder()
                .Content(content)
                .FromUserName(this.loginSession.getUserName())
                .ToUserName(toUserName)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Type(MessageType.TEXT.getCode())
                .build();
        return new SendTextRequest(this.loginSession, message).execute(this.engine);
    }

    @Override
    public boolean sendEmoticon(String toUserName, String emoticonMd5) {
        String id = System.currentTimeMillis() / 1000 + RandomUtil.randomString(6);
        SendMessage message = SendMessage.builder()
                .EMoticonMd5(emoticonMd5)
                .FromUserName(this.loginSession.getUserName())
                .ToUserName(toUserName)
                .LocalID(id)
                .ClientMsgId(id)
                .Type(MessageType.EMOTICON.getCode())
                .build();
        return new SendEmoticonRequest(this.loginSession, message).execute(this.engine);
    }

    @SneakyThrows
    @Override
    public boolean sendImage(String toUserName, File file) {
        UploadResult result = this.uploadMedia(toUserName, file);
        String msgId = System.currentTimeMillis() + RandomUtil.randomString("1234567890", 4);
        SendMessage message = SendMessage.builder()
                .Type(MessageType.IMAGE.getCode())
                .FromUserName(this.loginSession.getUserName())
                .ToUserName(toUserName)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Content("")
                .MediaId(result.getMediaId())
                .build();
        return new SendImageRequest(this.loginSession, message).execute(this.engine);
    }

    @Override
    public boolean sendVideo(String toUserName, File file) {
        UploadResult result = this.uploadMedia(toUserName, file);
        Assert.notBlank(result.getMediaId(), () -> new ApiException("视频上传失败，MediaId为空"));

        String msgId = System.currentTimeMillis() + RandomUtil.randomString("1234567890", 4);
        SendMessage message = SendMessage.builder()
                .Type(MessageType.VIDEO.getCode())
                .FromUserName(this.loginSession.getUserName())
                .ToUserName(toUserName)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Content("")
                .MediaId(result.getMediaId())
                .build();
        return new SendVideoRequest(this.loginSession, message).execute(this.engine);
    }

    @Override
    public boolean sendFile(String toUserName, File file) {
        UploadResult result = this.uploadMedia(toUserName, file);
        Assert.notBlank(result.getMediaId(), () -> new ApiException("文件上传失败，mediaId为空"));

        String title = FileUtil.getName(file);
        String suffix = FileUtil.getSuffix(file);
        String msgId = System.currentTimeMillis() + RandomUtil.randomString("1234567890", 4);
        String content = StrUtil.format("<appmsg appid='wxeb7ec651dd0aefa9' sdkver=''>" +
                        "<title>{}</title><des></des><action></action><type>6</type><content></content><url></url><lowurl></lowurl>" +
                        "<appattach><totallen>{}</totallen><attachid>{}</attachid><fileext>{}</fileext></appattach><extinfo></extinfo></appmsg>",
                title, result.getStartPos(), result.getMediaId(), suffix);
        SendMessage message = SendMessage.builder()
                .Type(MessageType.FILE.getCode())
                .FromUserName(this.loginSession.getUserName())
                .ToUserName(toUserName)
                .LocalID(msgId)
                .ClientMsgId(msgId)
                .Content(content)
                .build();
        return new SendFileRequest(this.loginSession, message).execute(this.engine);
    }

    @Override
    public UploadResult uploadMedia(String toUserName, File file) {
        Assert.isTrue(FileUtil.isFile(file) && FileUtil.size(file) > 0, "文件不存在或大小为0");
        log.debug("上传文件：{}", file.getName());
        return new UploadMediaRequest(this.loginSession, file, toUserName).execute(this.engine);
    }

    @Override
    public byte[] getImage(Long mediaId) {
        return new GetImageRequest(this.loginSession, mediaId).execute(this.engine);
    }

    @Override
    public byte[] getVideo(Long mediaId) {
        return new GetVideoRequest(this.loginSession, mediaId).execute(this.engine);
    }

    @Override
    public byte[] getVoice(Long mediaId) {
        return new GetVoiceRequest(this.loginSession, mediaId).execute(this.engine);
    }

    @Override
    public List<Contact> getContactList() {
        return new ContactListRequest(this.loginSession).execute(this.engine);
    }

    @Override
    public List<Contact> batchGetContactList(List<BatchContactQuery> queryList) {
        return new BatchContactRequest(this.loginSession, queryList).execute(this.engine);
    }

    @Override
    public Contact getContactById(String id) {
        BatchContactQuery build = BatchContactQuery.builder().EncryChatRoomId("").UserName(id).build();
        List<BatchContactQuery> singleton = Collections.singletonList(build);
        return this.batchGetContactList(singleton).get(0);
    }

    @Override
    public Contact getContactByName(String name) {
        return this.context.getContactManager().getContactByRemarkName(name);
    }

    @Override
    public boolean verify(Recommend recommend) {
        return new VerifyRequest(this.loginSession, recommend).execute(this.engine);
    }

    @Override
    public boolean addFriend(String userName, String content) {
        return new AddFriendRequest(this.loginSession, userName, content).execute(this.engine);
    }

    @Override
    public boolean modifyContactRemark(String remarkName, String username) {
        return new ModifyContactRemarkRequest(this.loginSession, remarkName, username).execute(this.engine);
    }

    @Override
    public boolean createGroup(String groupName, List<String> members) {
        return new CreateGroupRequest(this.loginSession, groupName, members).execute(this.engine);
    }

    @Override
    public boolean removeMember(String groupName, String memberUserName) {
        return new RemoveMemberRequest(this.loginSession, groupName, memberUserName).execute(this.engine);
    }

    @Override
    public boolean inviteMember(String groupName, String memberUserName) {
        return new InviteMemberRequest(this.loginSession, groupName, memberUserName).execute(this.engine);
    }

    @Override
    public boolean modifyGroupName(String groupName, String newGroupName) {
        Contact group = this.getContactByName(groupName);
        Assert.notNull(group, () -> new ApiException("找不到群: [" + groupName + "] 更换群名失败"));
        return new ModifyGroupNameRequest(this.loginSession, newGroupName, group).execute(this.engine);
    }

    @Override
    public List<Member> getGroupMemberList(String groupName) {
        List<BatchContactQuery> list = List.of(BatchContactQuery.builder().EncryChatRoomId("").UserName(groupName).build());
        // 查询返回
        return this.batchGetContactList(list).get(0).getMemberList();
    }


}
